﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NF.Core.Fuzzy
{
    public class FuzzyController
    {
        public FuzzyController(IEnumerable<Rule> rules, DomainPartition[] domains)
        {
            mDomains = domains;

            mRuleSet = rules;
        }

        public FuzzyController(IEnumerable<NPoint> learningData, int K)
        {
            int[] Ka = new int[learningData.First().Order];
            for (int i = 0; i < Ka.Length; ++i)
                Ka[i] = K;

            PartitionDomains(learningData, Ka);

            BuildRuleSet(learningData);
            BuildRuleMatrix(mRuleSet);
        }

        private String GetLabel(int i, int j)
        {
            return String.Format("P{0}{1}", i, j);
        }

        private void PartitionDomains(IEnumerable<NPoint> learningData, int[] K)
        {
            mDomains = new DomainPartition[learningData.First().Order];
            var domainRanges = CalculateSignalDomainRanges(learningData);

            double span, blockSpan, center;
            int k;

            for (int i = 0; i < mDomains.Length; ++i)
            {
                k = K[i];

                span = domainRanges[i].B - domainRanges[i].A;
                blockSpan = span / (double)k;
                mDomains[i] = new DomainPartition(k + 1);
                center = domainRanges[i].A;

                for (int j = 0; j <= k; ++j, center += blockSpan)
                {
                    mDomains[i].Add(new TriangularFuzzySet(center - blockSpan, center + blockSpan)
                    {
                        Label = GetLabel(i, j)
                    });
                }
            }
        }

        public FuzzyController(IEnumerable<NPoint> learningData, int[] K)
        {
            PartitionDomains(learningData, K);

            BuildRuleSet(learningData);
            BuildRuleMatrix(mRuleSet);
        }

        public FuzzyController(IEnumerable<NPoint> learningData, DomainPartition[] domains)
        {
            mDomains = domains;

            BuildRuleSet(learningData);
            BuildRuleMatrix(mRuleSet);
        }

        private void BuildRuleSet(IEnumerable<NPoint> learningData)
        {
            var conflicts = from r in
                               (from itm in learningData select BuildRule(itm))
                           group r by r.A.Signature into conflict
                           select conflict.ToArray();

            var rules = from r in
                            (from itm in learningData select BuildRule(itm))
                        group r by r.A.Signature into conflict
                        let maxtr = conflict.Max(c => c.B)
                        select conflict.Where(r => r.B == maxtr).First().A;

            mRuleSet = rules;
        }

        private double CalcTruthFactor(Rule rule, NPoint pt)
        {
            double tr = 1.0;

            int i;
            for (i = 0; i < rule.Order; ++i)
                tr *= rule[i].DetermineMembership(pt.V[i]);
            tr *= rule.Conclusion.DetermineMembership(pt.V[i]);

            return tr;
        }

        private Pair<Rule, double> BuildRule(NPoint item)
        {
            IFuzzySet[] premises = new IFuzzySet[item.Order - 1];

            int i;
            for (i = 0; i < premises.Length; ++i)
                premises[i] = FindSetWithHighestMembership(item.V[i], mDomains[i]);

            Rule rule = new Rule(premises);
            rule.Conclusion = FindSetWithHighestMembership(item.V[i], mDomains[i]);

            return new Pair<Rule, double>()
            {
                A = rule,
                B = CalcTruthFactor(rule, item)
            };
        }

        private static IFuzzySet FindSetWithHighestMembership(double x, DomainPartition dp)
        {
            return dp.OrderBy(s => s.DetermineMembership(x)).Last();
        }

        private static Pair<double, double>[] CalculateSignalDomainRanges(
            IEnumerable<NPoint> learningData)
        {
            Pair<double, double>[] domains = new Pair<double, double>[learningData.First().Order];
            for (int i = 0; i < domains.Length; ++i)
            {
                domains[i] = new Pair<double, double>()
                {
                    A = (from x in learningData select x.V[i]).Min(),
                    B = (from x in learningData select x.V[i]).Max()
                };
            }

            return domains;
        }

        public double Evaluate(NPoint pt)
        {
            double p = 0.0, q = 0.0, a;

            foreach (var rule in mRuleSet)
            {
                a = ActivityLevel(rule, pt);
                p += (a * rule.Conclusion.Maximum);
                q += a;
            }

            return p / q;
        }

        private double ActivityLevel(Rule r, NPoint pt)
        {
            double a = 1.0f;
            for (int i = 0; i < r.Order; ++i)
                a *= r[i].DetermineMembership(pt[i]);

            return a;
        }

        private void BuildRuleMatrix(IEnumerable<Rule> rules)		
        {		
            int ruleOrder = rules.First().Order;		
            if (mDomains.Length != ruleOrder + 1)		
                throw new ArgumentException();		
		
            int[] tmp = new int[ruleOrder];
            for (int i = 0; i < ruleOrder; ++i)
                tmp[i] = mDomains[i].Count;

            mRuleGrid = Array.CreateInstance(typeof(IFuzzySet), tmp);		
		
            foreach (var rule in rules)		
            {
                for (int i = 0; i < ruleOrder; ++i)
                    tmp[i] = mDomains[i].IndexOf(rule[i]);
		
                mRuleGrid.SetValue(rule.Conclusion, tmp);		
            }		
        }

        public int[] GetRuleMatrixDimensions()
        {
            int[] dimensions = new int[mRuleGrid.Rank];
            for (int i = 0; i < dimensions.Length; ++i)
                dimensions[i] = mRuleGrid.GetLength(i);

            return dimensions;
        }

        public IFuzzySet GetRuleFromMatrix(params int[] p)
        {
            return (IFuzzySet)mRuleGrid.GetValue(p);
        }

        private Array mRuleGrid;
        private IEnumerable<Rule> mRuleSet; 
        private DomainPartition[] mDomains;
    }
}
